/*
* Copyright (C) 2008-2009 
* J-P Nurmi jpnurmi@gmail.com
* V Jousse vjousse@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* $Id: application.cpp 293 2009-02-15 09:52:20Z vjousse $
*/

#include "viewer.h"
#include <QTreeWidget>
#include <QTime>
#include <QModelIndex>

#include "controller.h"
#include "messageview.h"
#include "channelinfo.h"
#include "application.h"
#include "userinfo.h"
#include "server.h"

#include "viewmodel.h"
#include "ircmodel.h"
#include "ircmodelitem.h"

#include "debugIrcSession.h"

#include "messageparser.h"
#include "completionmodel.h"
#include "mainwindow.h"

#include <ircsession.h>
#include <irc.h>

Q_DECLARE_METATYPE(Irc::Session*)
Q_DECLARE_METATYPE(MessageParser*)

Controller::Controller(QObject* parent)
	: QObject(parent)
{
	d.model = new IrcModel(this);
    d.viewModel = new ViewModel(d.model);
    MainWindow::instance()->viewTree()->setModel(d.viewModel);
/*
    connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
        this, SLOT(onIconActivated(QSystemTrayIcon::ActivationReason)));
*/

	connect(MainWindow::instance()->viewTree()->selectionModel(),
            SIGNAL(currentChanged(QModelIndex, QModelIndex)),
			SLOT(currentItemChanged(QModelIndex)));

    connect(MainWindow::instance()->viewer(),
            SIGNAL(currentViewChanged(MessageView*)),
            SLOT(currentViewChanged(MessageView*)), Qt::QueuedConnection);

}

void Controller::connectTo(const Server& server)
{
    UserInfo user = UserInfo::fromString(server.user);
	qDebug() << "Start / Connection requested to controller";
	
	Irc::Session* session = new Irc::Session(this);
    session->setAutoReconnectDelay(10);
    session->setOptions(Irc::Session::StripNicks);
    QString appName = QApplication::applicationName();
    session->setNick(user.nick);
    session->setIdent(appName.toLower());
    session->setRealName(appName);
    session->setPassword(server.password);
    session->connectToServer(server.host, server.port);

	DebugIrcSession* debugSession = new DebugIrcSession();
	session->connectSlotsByName(debugSession);
	session->connectSlotsByName(this);
		
	MessageParser* parser = new MessageParser(this);
	parser->connectSlotsByName(this);
	parser->connectSlotsByName(session);
    session->setProperty("parser", QVariant::fromValue(parser));

	qDebug() << "End / Connection requested to controller";
}

void Controller::onConnectRequested(const Server& server)
{
	connectTo(server);
}

void Controller::on_connected()
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
    d.model->connected(session);
}

void Controller::on_disconnected()
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
    d.model->disconnected(session);
}

void Controller::on_msgJoined(const QString& origin, const QString& channel)
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
	
    d.model->joined(session, origin, channel);
	
    //prepareTarget(origin, channel, true, settings->showJoins());
	
    foreach (MessageView* view, d.messageViews)
    {
		
        bool originMatches = view->matches(origin);
        bool channelMatches = view->matches(channel);
        if (originMatches || channelMatches)
        {
            //if (settings->showJoins())
                view->logMessage(origin, "! %1 joined %2", channel);
        }
    }
}

void Controller::on_msgParted(const QString& origin, const QString& channel, const QString& message)
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
	
    foreach (MessageView* view, d.messageViews)
    {
        bool originMatches = view->matches(origin);
        bool channelMatches = view->matches(channel);
        if (originMatches || channelMatches)
        {
/*            
if (settings->showParts())
            {
	*/
                prepareTarget(origin, view->receiver(), session, false); // don't create
                view->logMessage(origin, "! %1 parted %2", QString("%1 (%2)").arg(channel).arg(message));
      //      }
        }
    }
    int res = origin.compare(session->nick(), Qt::CaseInsensitive);
    d.model->parted(session, origin, channel, message, res == 0);
}


void Controller::on_msgQuit(const QString& origin, const QString& message)
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
	
    foreach (MessageView* view, d.messageViews)
    {
        bool matches = view->matches(origin);
        bool contains = view->contains(origin);

        if (matches || contains)
        {
            //if (settings->showQuits())
            //{
                prepareTarget(origin, view->receiver(), session, false); // don't create
                view->logMessage(origin, "! %1 has quit (%2)", message);
            //}
        }
    }
    d.model->quit(session, origin, message);
}

void Controller::on_msgNickChanged(const QString& origin, const QString& nick)
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
	
    foreach (MessageView* view, d.messageViews)
    {
        bool matches = view->matches(origin);
        bool contains = view->contains(origin);
        if (matches || contains)
        {
            //if (settings->showNicks())
            //{
                prepareTarget(origin, view->receiver(),session,  false); // don't create
                view->logMessage(origin, "! %1 changed nick to %2", nick);
            //}
            if (matches)
            {
	//TODO : Change the nick of the appropriate message view
	/*
				QString targetId = getTargetId(view->receiver(),session);
				
                views[nick.toLower()] = views.take(view->receiver().toLower());
                setTabText(indexOf(view), nick);
                view->setReceiver(nick);
	*/
            }
        }
    }
    d.model->nickChanged(session, origin, nick);
}


void Controller::on_msgModeChanged(const QString& origin, const QString& receiver, const QString& mode, const QString& args)
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
	
    //QString target = prepareTarget(origin, receiver, session, true, settings->showModes());
    
	QString target = prepareTarget(origin, receiver, session, true, true);
	uint targetId = getTargetId(target,session);
	qDebug() << "searching view with id " << targetId << " " << d.messageViews[targetId];
	//if (settings->showModes())
        d.messageViews[targetId]->logMessage(origin, "! %1 %2", QString("sets mode %1 %2").arg(mode).arg(args));

    d.model->channelModeChanged(session, origin, receiver, mode, args);
}

void Controller::on_msgTopicChanged(const QString& origin, const QString& channel, const QString& topic)
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
	
    QString target = prepareTarget(origin, channel, session);
	uint targetId = getTargetId(target,session);
    d.messageViews[targetId]->logMessage(origin, "! %1 %2", QString("sets topic '%1'").arg(topic));

    d.model->topicChanged(session, origin, channel, topic);
}

void Controller::on_msgInvited(const QString& origin, const QString& receiver, const QString& channel)
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
	
    MessageView* view = MainWindow::instance()->viewer()->currentView();
    view->logMessage(origin, "! %1 %2", QString("invited %1 to %2").arg(receiver).arg(channel));

    d.model->invited(session, origin, receiver, channel);
}

void Controller::on_msgKicked(const QString& origin, const QString& channel, const QString& nick, const QString& message)
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
	
//    QString target = prepareTarget(origin, channel, true, settings->showKicks());
	QString target = prepareTarget(origin, channel, session, true, true);
	uint targetId = getTargetId(target,session);
	
    //if (settings->showKicks())
        d.messageViews[targetId]->logMessage(origin, "! %1 %2", QString("kicked %1 (%2)").arg(nick).arg(message));

    d.model->kicked(session, origin, channel, nick, message);
}


void Controller::on_msgMessageReceived(const QString& origin, const QString& receiver, const QString& message)
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
	
    QString target = prepareTarget(origin, receiver,session);
	uint targetId = getTargetId(target,session);
	
    bool highlight = message.contains(session->nick());
/*
    if (highlight)
    {
        if (tabText(currentIndex()).toLower() != target)
            alertees += target;
        alert();
    }
*/
    d.messageViews[targetId]->receiveMessage(origin, message, highlight);

	//Check if the message received is for the current view
	if(d.messageViews[targetId] == MainWindow::instance()->viewer()->currentView())
    	d.model->channelMessageReceived(session, origin, receiver, message,true);
	else
		d.model->channelMessageReceived(session, origin, receiver, message,false);
}

void Controller::on_msgNoticeReceived(const QString& origin, const QString& receiver, const QString& message)
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
	
    QString target = prepareTarget(origin, receiver,session);
	uint targetId = getTargetId(target,session);

    bool highlight = message.contains(session->nick());
    /*if (highlight)
        alert();
*/

   d.messageViews[targetId]->receiveNotice(origin, message, highlight);

   d.model->noticeReceived(session, origin, receiver, message);
}

void Controller::on_msgCtcpRequestReceived(const QString& origin, const QString& request)
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
	
    MessageView* view = MainWindow::instance()->viewer()->currentView();
    view->logMessage(origin, "! %1 %2", QString("requested CTCP-%1").arg(request.split(QRegExp("\\s")).first().toUpper()));
    if (!request.compare("VERSION", Qt::CaseInsensitive))
        session->cmdCtcpReply(origin, QString("%1 %2 %3").arg(request).arg(qApp->applicationName()).arg(qApp->applicationVersion()));
    else if (request.startsWith("PING", Qt::CaseInsensitive))
        session->cmdCtcpReply(origin, request);
    else if (request.startsWith("TIME", Qt::CaseInsensitive))
        session->cmdCtcpReply(origin, QString("%1 %2").arg(request).arg(QDateTime::currentDateTime().toString()));
    else
        qFatal("Controller::on_ctcpRequestReceived '%s' requested '%s'", qPrintable(origin), qPrintable(request));

    d.model->ctcpRequestReceived(session, origin, request);
}

void Controller::on_msgCtcpReplyReceived(const QString& origin, const QString& reply)
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
	
    QString message = reply;
    if (message.startsWith("PING", Qt::CaseInsensitive))
    {
        QStringList params = message.split(QRegExp("\\s"), QString::SkipEmptyParts);
        if (!params.isEmpty())
        {
            QDateTime dateTime = QDateTime::fromTime_t(params.last().toInt());
            if (dateTime.isValid())
            {
                QDateTime current = QDateTime::currentDateTime();
                int msecs = dateTime.time().msecsTo(current.time());
                if (msecs < 1000)
                    message = QString("PING %1ms").arg(msecs);
                else if (msecs < 60000)
                    message = QString("PING %1s").arg(msecs / 1000.0, 0, 'f', 1);
                else
                    message = QString("PING %1m").arg(msecs / 60000);
            }
        }
    }

    MessageView* view = MainWindow::instance()->viewer()->currentView();
    view->logMessage(origin, "! %1 %2", QString("replied CTCP-%1").arg(message));

    d.model->ctcpReplyReceived(session, origin, message);
}

void Controller::on_msgCtcpActionReceived(const QString& origin, const QString& receiver, const QString& action)
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
	
    QString target = prepareTarget(origin, receiver,session);
	uint targetId = getTargetId(target,session);
	
    bool highlight = action.contains(session->nick());
    
	//if (highlight)
    //    alert();
    d.messageViews[targetId]->receiveAction(origin, action, highlight);

    d.model->ctcpActionReceived(session, origin, receiver, action);
}

void Controller::on_msgNumericMessageReceived(const QString& origin, uint code, const QStringList& params)
{
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
	
    d.model->numericMessageReceived(session, origin, code, params);

    MessageView* currentView = MainWindow::instance()->viewer()->currentView();
    if (!currentView)
        prepareTarget(origin, QString(), session);
    currentView = MainWindow::instance()->viewer()->currentView();
    switch (code)
    {
    case Irc::Rfc::RPL_WELCOME:
        {
            prepareTarget(origin, QString(), session);
        }
        break;

    case Irc::Rfc::RPL_WHOISUSER:
        {
            currentView->logMessage(params.value(1), "! %1 is %2", QString("%1@%2 (%3)").arg(params.value(2)).arg(params.value(3)).arg(params.value(5)));
        }
        return;

    case Irc::Rfc::RPL_WHOISSERVER:
        {
            currentView->logMessage(params.value(1), "! %1 is online via %2", QString("%1 (%2)").arg(params.value(2)).arg(params.value(3)));
        }
        return;

    case Irc::Rfc::RPL_WHOISOPERATOR:
        {
            qDebug() << "numeric:" << origin << code << params;
            Q_ASSERT(false);
            //currentView->logMessage(params.value(1), "! %1 is online via %2", QString("%1 (%2)").arg(params.value(2)).arg(params.value(3)));
        }
        return;

    case Irc::Rfc::RPL_WHOWASUSER:
        {
            qDebug() << "numeric:" << origin << code << params;
            Q_ASSERT(false);
            //currentView->logMessage(params.value(1), "! %1 is online via %2", QString("%1 (%2)").arg(params.value(2)).arg(params.value(3)));
        }
        return;

    case Irc::Rfc::RPL_WHOISIDLE:
        {
            QTime idle = QTime().addSecs(params.value(2).toInt());
            currentView->logMessage(params.value(1), "! %1 has been idle for %2", idle.toString());

            QDateTime signon = QDateTime::fromTime_t(params.value(3).toInt());
            currentView->logMessage(params.value(1), "! %1 has been online since %2", signon.toString());
        }
        return;

    case Irc::Rfc::RPL_ENDOFWHOIS:
        {
            // End of /WHOIS list.
        }
        return;

    case Irc::Rfc::RPL_WHOISCHANNELS:
        {
            currentView->logMessage(params.value(1), "! %1 is on channels %2", params.value(2));
        }
        return;

    case 320:
        {
            currentView->logMessage(params.value(1), "! %1 %2", params.value(2));
        }
        return;

    case Irc::Rfc::RPL_CHANNELMODEIS:
        {
            currentView->logMessage(params.value(1), "! %1 mode is %2", params.value(2));
        }
        return;

    case 329:
        {
            QDateTime dateTime = QDateTime::fromTime_t(params.value(2).toInt());
            currentView->logMessage(params.value(1), "! %1 was created %2", dateTime.toString());
        }
        return;

    case Irc::Rfc::RPL_NOTOPIC:
        {
            QString target = prepareTarget(QString(), params.value(1),session);
			uint targetId = getTargetId(target,session);
            d.messageViews[targetId]->logMessage(QString(), "%1! no topic set%2", QString());
        }
        return;

    case Irc::Rfc::RPL_TOPIC:
        {
            QString target = prepareTarget(QString(), params.value(1),session);
			uint targetId = getTargetId(target,session);
            d.messageViews[targetId]->logMessage(QString(), "%1! topic is '%2'", params.value(2));
            d.messageViews[targetId]->setTopic(params.value(2));
        }
        return;

    case Irc::Rfc::RPL_INVITING:
        {
            QString target = prepareTarget(QString(), params.value(2),session);
			uint targetId = getTargetId(target,session);
            d.messageViews[targetId]->logMessage(QString(), "%1! inviting %2", QString("%1 to %2").arg(params.value(1)).arg(params.value(2)));
        }
        return;

    case 333:
        {
            QDateTime dateTime = QDateTime::fromTime_t(params.value(3).toInt());
            if (dateTime.isValid())
            {
                QString target = prepareTarget(QString(), params.value(1),session);
				uint targetId = getTargetId(target,session);
                d.messageViews[targetId]->logMessage(params.value(2), "! topic set %2 by %1", dateTime.toString());
            }
        }
        return;

    case Irc::Rfc::RPL_VERSION:
        {
            currentView->logMessage(origin, "! %1 version is %2", params.value(1));
        }
        return;

    case Irc::Rfc::RPL_NAMREPLY:
        {
            QStringList list = params;
            list.removeAll("=");
            list.removeAll("@");

			QString target = prepareTarget(QString(), list.value(1),session);
            QStringList nicks = list.value(2).split(" ", QString::SkipEmptyParts);
            QString msg = QString("[ %2 ]").arg(nicks.join(" ] [ "));
			uint targetId = getTargetId(target,session);
            d.messageViews[targetId]->logMessage(QString(), "%1! %2", msg);
            //tabActivated(currentIndex());
        }
        return;

    case Irc::Rfc::RPL_ENDOFNAMES:
        return;

    case Irc::Rfc::RPL_TIME:
        {
            currentView->logMessage(params.value(1), "! %1 time is %2", params.value(2));
        }
        return;

    default:
        break;
    }

    //qDebug() << "numeric:" << origin << code << params << views;
    currentView->logMessage(QString::number(code), "[%1] %2", params.last());
}


void Controller::on_msgUnknownMessageReceived(const QString& origin, const QStringList& params)
{
    // TODO
    qWarning() << "unknown:" << origin << params;
	Irc::Session* session = qobject_cast<Irc::Session*>(sender());
    Q_ASSERT(session);
	
    d.model->unknownMessageReceived(session, origin, params);
}


void Controller::logMessage(const QString& receiver, const QString& message)
{
	qDebug() << receiver << " - " << message;
	//on_msgMessageReceived(session->nick(), receiver, message);
}

void Controller::send(const QString& message)
{
    if (message.trimmed().isEmpty())
        return;

	MessageView* view = qobject_cast<MessageView*>(sender());
    Q_ASSERT(view);
    Irc::Session* session = view->property("session").value<Irc::Session*>();
    Q_ASSERT(session);
    MessageParser* parser = session->property("parser").value<MessageParser*>();
	parser->setProperty("session",QVariant::fromValue(session));
	
    QString defaultReceiver = view->receiver();
	//TODO : uncomment
    //parser->setReceivers(d.messageViews.keys());
    parser->parse(message, defaultReceiver);
}

void Controller::openView(const QString& receiver)
{
	MessageParser* parser = qobject_cast<MessageParser*>(sender());
	Q_ASSERT(parser);
	Irc::Session* session = parser->property("session").value<Irc::Session*>();
	
	openView(receiver,session);
}

void Controller::openView(const QString& receiver, Irc::Session* session)
{
	//Create a new view, i.e. irc text, line edit to send commands, and text buttons
    //MessageView* view = new MessageView(receiver, this);

	//QString target = prepareTarget(receiver, receiver,session);
	MessageView* view = MainWindow::instance()->viewer()->addMessageView(receiver);
    view->setProperty("session", QVariant::fromValue(session));
	uint id = getTargetId(receiver,session);
	view->setId(id);
	
	//Will be used to send messages written in the lineEdit to the parser
	//the parser will emit the correct signal for the session or the view
    connect(view, SIGNAL(send(QString)), this, SLOT(send(QString)));

	//Add the view to the view list
    d.messageViews.insert(id,view);

    CompletionModel* proxyModel = new CompletionModel(view);

	//Create the entry into the model for this session
    IrcModelItem* sessionItem = d.model->addSession(session, receiver);
    if (sessionItem->name() == receiver)
    {
        proxyModel->setCompletionIndex(sessionItem->index());
    }
    else
    {
        IrcModelItem* receiverItem = sessionItem->childItem(receiver);
        proxyModel->setCompletionIndex(receiverItem->index());
    }
    view->setModel(proxyModel);
}

QString Controller::prepareTarget(const QString& sender, const QString& receiver, Irc::Session* session, bool create, bool inform)
{
	
    QString target = receiver;
	
    if (target.contains("*") || target.contains("?"))
        target = session->nick();

    if (target == session->nick())
    {
        if (target == sender)
            target = session->host();
        else
            target = sender;
    }

    if (target.isEmpty() || target == "AUTH")
        target = sender;

	target=target.toLower();

	uint id = getTargetId(target,session);
	
    if (create && !d.messageViews.contains(id)) {
		qDebug() << "### Creating view with id " << id;
        openView(target,session);
	}
/*
    int index = indexOf(views[target.toLower()]);
    if (inform && index != currentIndex())
    {
        setTabIcon(index, QApplication::windowIcon());
        emit tabIconChanged(QApplication::windowIcon());
    }
*/
	return target;
}

uint Controller::getTargetId(const QString& target,Irc::Session* session)
{
	uint id = qHash(target.toLower());
	uint sessionId = qHash(session);
	id=id^sessionId;
	
	return id;
}

/*!
Called when the current item of the treeView is changed
*/

void Controller::currentItemChanged(const QModelIndex& index)
{
	//Find the item
    QModelIndex sourceIndex = d.viewModel->mapToSource(index);
	IrcModelItem* item = static_cast<IrcModelItem*>(d.model->itemFromIndex(sourceIndex));
    Q_ASSERT(item);
    Irc::Session* session = item->session();
    Q_ASSERT(session);
	
    //If the color was red (new message) put it back to black (ACDC !)
    QBrush brush = item->foreground();
    brush.setColor(Qt::black);
    item->setForeground(brush);

    //Switch the current view
    uint id = getTargetId(item->name(), session);
    if (d.messageViews.contains(id)) {
        MainWindow::instance()->viewer()->setCurrentView(d.messageViews.value(id));
    }
}

void Controller::currentViewChanged(MessageView* view)
{
    Irc::Session* session = view->property("session").value<Irc::Session*>();
    Q_ASSERT(session);
    IrcModelItem* sessionItem = d.model->addSession(session);
    Q_ASSERT(sessionItem);
    IrcModelItem* item = sessionItem->findChild(view->receiver());
    
    if (item)
    {
        if (!MainWindow::instance()->nickList()->model())
            MainWindow::instance()->nickList()->setModel(d.model);
        MainWindow::instance()->nickList()->setRootIndex(item->index());
    }
    else
    {
        if (MainWindow::instance()->nickList()->model())
            MainWindow::instance()->nickList()->setModel(0);
    }
}
